//-------------------------------------------
// Tech Demo Server Program
// 1. Wait for x players to connect
// 2. Send a text message to each player when another connects with id
// 3. Wait for a key press translated to a specific message for "go"
// 4. Staring game :
// 4.1 recieve messages with commands
// 4.2 bounce out all messages again
// 4.3 each player sends each n:th frame an ack
// 4.4 when all players has sent an ack for frame x then broadcast. x done - repeat.
//-------------------------------------------

#include "../net/RtsConnection.h"

#include <string>
#include <sstream>
#include <iostream>
#include <map>
#include <vector>

using namespace std;
using namespace TNL;
using namespace Putin;

#define ver true   // verbose

/*!
 * Player id and state information
 */
struct PlayerInfo
{
	PlayerInfo()
	{
		id = 0; // invalid..
		go = false;
		lastFrame = 0;
	}
	int id;						// id (should be a name here.. )
	bool go;					// ready to start the game?
	unsigned short lastFrame;	// last frame synced
};

/*!
 * \brief
 * Wrapper for functions that handle the connection events..
 */
class Server
{
public:

	enum State
	{
		WAITING_GO = 1,
		RUNNING = 2
	};

	Server()
	{
		mNextPlayerId = 1;
		mState = WAITING_GO;
	}
	/*!
	 * \brief
	 * Handle incomming packet event.
	 * 
	 * \param net
	 * The connection this event refers to
	 * 
	 * \param p
	 * The incomming NetworkPacket
	 */
	void handlePacket(NetworkConnection* net, NetworkPacket* p)
	{
		// Translate packet
		if( p->getType() == NetworkPacket::npText)
		{
			NetworkText* nt = dynamic_cast<NetworkText*>(p);
			
 			cout << "From: " << net->getRemoteAddress() << ">> " << nt->text << endl;
			cout << "-----------------------------------------" << endl;

			// pass on
			net->broadcast(nt,false);
		}
		else if( p->getType() == NetworkPacket::npCommand)
		{
			NetworkCommand* nc = dynamic_cast<NetworkCommand*>(p);
			// depending on state, do different things
			/*if( ver )
				cout << "Command: " << nc->command << endl;*/

			if( mState == WAITING_GO ) // Initial phase, wait for players to join "LObBY"
			{
				if( nc->command == NC_GO )
				{
					mPlayers[net->getRemoteAddress()].go = true;
					if( ver )
						cout << "Player " << mPlayers[net->getRemoteAddress()].id << " is ready " << endl;

					// Check if all players has okayed GO..
					bool go = true;
					for each( pair<string,PlayerInfo> pi in mPlayers)
						go = go && pi.second.go;

					if( go )
					{
						NetworkCommand goPacket(NC_GO,0,0,0,0);
						net->broadcast(&goPacket,false);
						mState = RUNNING; // Start game!!!
						cout << "Game is started!" << endl;
					}
				}
			}
			else if( mState == RUNNING )
			{
				if( nc->command == NC_FRAME ) // frame ack!
				{
					cout << "sync?\n"; 
					// Wait for sync!!!
					string ra = net->getRemoteAddress();
					mPlayers[ra].lastFrame = nc->id1;
					
					bool sync = true;
					// check if all players has the same last frame set
					for each( pair<string,PlayerInfo> pi in mPlayers)
						sync = sync && pi.second.lastFrame == nc->id1;	

					if( sync )
					{
						// broadcast continue
						NetworkCommand ok(NC_FRAME,nc->id1,0,0.0f,0.0f);
						net->broadcast(&ok,false);
						cout << "sync!\n";
					}

				}
				else // all other commands.. Attack, move ..
				{
					// simply broadcast.. I guess
					net->broadcast(nc,false); // bc to all, even the sender
				}
			}
		}
	}

	/*!
	 * \brief
	 * A client has connected!
	 * 
	 * \param net the refering connection
	 * 
	 * \param p Not used
	 */
	void handleOnConnected(NetworkConnection* net, NetworkPacket* p)
	{
		cout << "Client " << net->getRemoteAddress() << " has connected" << endl;
	
		if( mState == WAITING_GO )
		{
			int id = mNextPlayerId++;
			// probably should keep a track of players somewhere. a map?
			//mPlayers[net->getRemoteAddress()].id = id;
			NetworkCommand newPlayerInfo(NC_PLAYER,id,0,0.0f,0.0f);
			net->broadcast(&newPlayerInfo);
			NetworkCommand playerReply(NC_PLAYER_R,id,0,0.0f,0.0f);
			net->send2Client(&playerReply);

			// prob. should send a list of all clients to the connected client also
			for each ( pair<string,PlayerInfo> pi in mPlayers )
			{
				NetworkCommand playerInfo(NC_PLAYER,pi.second.id,0,0.0f,0.0f);
				net->send2Client(&playerInfo);
			}
			// add the player to the map here
			mPlayers[net->getRemoteAddress()] = PlayerInfo();
			mPlayers[net->getRemoteAddress()].id = id;
		}
		else
		{
			RtsConnection* r = static_cast<RtsConnection*>(net);
			r->disconnect("Game running already..");
		}
	}

	/*!
	 * \brief
	 * A client has disconnected!
	 * 
	 * \param net the refering connection
	 * 
	 * \param p Not used
	 */
	void handleOnDisconnect(NetworkConnection* net, NetworkPacket* p)
	{
		int id = mPlayers[net->getRemoteAddress()].id;
		cout << "Client " << id << " has disconnected" << endl;
		NetworkCommand playerDisc(NC_PLAYER_D,id,0,0.0f,0.0f);
		net->broadcast(&playerDisc); // Notify all other clients
		mPlayers.erase(net->getRemoteAddress());
	}

	int mNextPlayerId;
	State mState;
	std::map<std::string, PlayerInfo> mPlayers; // just id -> remote address
};


/*!
 * \brief
 * Main Server entry point
 * 
 * \param argc
 * Argcounter
 * 
 * \param argv
 * IP number should be passed as: 127.0.0.1:60000
 * 
 * \returns
 * simply 0
 */
int main(int argc, char** argv)
{
	cout << "Putin RTS Techdemo server v0.1\n";

	if( argc != 2 )
	{
		cout << "Usage: PutinServer <ip:port>, example PutinServer 127.0.0.1:79999" << endl;
		return 0;
	}
	Server s;

	Address cmdAddress(argv[1]);						// Input network address
	Putin::RtsConnection* conn = new RtsConnection();	//  The Putin RPC interface

	conn->startServer(cmdAddress);
	cout << "Server is started on " << cmdAddress.toString() << " waiting for clients " << endl;
	conn->addHandler( new NetFunctor<Server>(&Server::handlePacket,&s)); 
	conn->addOnConnectHandler(new NetFunctor<Server>(&Server::handleOnConnected, &s));
	conn->addOnDisconnectedHandler(new NetFunctor<Server>(&Server::handleOnDisconnect, &s));

	while(true)
	{
		conn->checkMessages();
		TNL::Platform::sleep(10); // to save some cpu
	}

	return 0;
}